﻿using Fidely.Framework;
using Fidely.Framework.Integration;
using Sasa.QualityTools.CodeAnalysis.FxCopIntegrator.ViewModels.Events;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Linq.Expressions;

namespace Sasa.QualityTools.CodeAnalysis.FxCopIntegrator.ViewModels
{
    public abstract class CodeAnalysisResultViewModelBase<TRowViewModel> : ViewModelBase
        where TRowViewModel : ViewModelBase, ICodeAnalysisResultRowViewModel
    {
        public event EventHandler<CodeFocusRequestedEventArgs> CodeFocusRequested;

        public event EventHandler ExportReportRequired;

        public event EventHandler GoToHowToSearchPageRequired;


        private static readonly int PageSize = 50;

        private int currentPage;

        private int totalPage;

        private int count;

        private bool canGoToPrevPage;

        private bool canGoToNextPage;

        private bool canBackHistory;

        private bool canForwardHistory;

        private SearchQueryCompiler<TRowViewModel> compiler;

        private Expression<Func<TRowViewModel, bool>> filter;

        private Func<TRowViewModel, object> sortKeySelector;

        private RowAnalysisResultRowViewModelComparer sortComparer;

        private ListSortDirection sortDirection;

        private Stack<SearchHistory> histories;

        private Stack<SearchHistory> backedHistories;


        public RelayCommand SearchCommand { get; private set; }

        public RelayCommand GoToFirstPageCommand { get; private set; }

        public RelayCommand GoToPrevPageCommand { get; private set; }

        public RelayCommand GoToNextPageCommand { get; private set; }

        public RelayCommand GoToLastPageCommand { get; private set; }

        public RelayCommand BackHistoryCommand { get; private set; }

        public RelayCommand ForwardHistoryCommand { get; private set; }

        public RelayCommand ExportReportCommand { get; private set; }

        public RelayCommand GoToHowToSearchPageCommand { get; private set; }

        public ICollection<TRowViewModel> Rows { get; private set; }

        public IEnumerable<TRowViewModel> CurrentPageRows { get; private set; }

        public ICollection<IAutoCompleteItem> AutoCompleteItems { get; private set; }

        public int CurrentPage
        {
            get
            {
                return currentPage;
            }
            private set
            {
                currentPage = value;
                OnPropertyChanged("CurrentPage");
            }
        }

        public int TotalPage
        {
            get
            {
                return totalPage;
            }
            private set
            {
                totalPage = value;
                OnPropertyChanged("TotalPage");
            }
        }

        public int Count
        {
            get
            {
                return count;
            }
            set
            {
                count = value;
                OnPropertyChanged("Count");
            }
        }

        public bool CanGoToPrevPage
        {
            get
            {
                return canGoToPrevPage;
            }
            set
            {
                canGoToPrevPage = value;
                OnPropertyChanged("CanGoToPrevPage");
            }
        }

        public bool CanGoToNextPage
        {
            get
            {
                return canGoToNextPage;
            }
            set
            {
                canGoToNextPage = value;
                OnPropertyChanged("CanGoToNextPage");
            }
        }

        public bool CanBackHistory
        {
            get
            {
                return canBackHistory;
            }
            set
            {
                canBackHistory = value;
                OnPropertyChanged("CanBackHistory");
            }
        }

        public bool CanForwardHistory
        {
            get
            {
                return canForwardHistory;
            }
            set
            {
                canForwardHistory = value;
                OnPropertyChanged("CanForwardHistory");
            }
        }


        protected CodeAnalysisResultViewModelBase(CompilerSetting compilerSetting)
        {
            compiler = SearchQueryCompilerBuilder.Instance.BuildUpCompiler<TRowViewModel>(compilerSetting);
            AutoCompleteItems = new ObservableCollection<IAutoCompleteItem>(compilerSetting.ExtractAutoCompleteItems());

            histories = new Stack<SearchHistory>();
            backedHistories = new Stack<SearchHistory>();

            sortComparer = new RowAnalysisResultRowViewModelComparer();

            Rows = new List<TRowViewModel>();
            CurrentPageRows = new ObservableCollection<TRowViewModel>();

            SearchCommand = new RelayCommand(o => Search(o as string));
            GoToFirstPageCommand = new RelayCommand(o => GoToFirstPage(), o => CanGoToPrevPage);
            GoToPrevPageCommand = new RelayCommand(o => GoToPrevPage(), o => CanGoToPrevPage);
            GoToNextPageCommand = new RelayCommand(o => GoToNextPage(), o => CanGoToNextPage);
            GoToLastPageCommand = new RelayCommand(o => GoToLastPage(), o => CanGoToNextPage);
            BackHistoryCommand = new RelayCommand(o => BackHistory(), o => CanBackHistory);
            ForwardHistoryCommand = new RelayCommand(o => ForwardHistory(), o => CanForwardHistory);
            ExportReportCommand = new RelayCommand(o => OnExportReportRequired(EventArgs.Empty), o => Rows.Count > 0);
            GoToHowToSearchPageCommand = new RelayCommand(o => OnGoToHowToSearchPageRequired(EventArgs.Empty), o => GoToHowToSearchPageRequired != null);
        }


        public void Search(string query)
        {
            AddSearchHistory();

            if (String.IsNullOrWhiteSpace(query))
            {
                filter = null;
            }
            else
            {
                filter = compiler.Compile(query);
            }

            CurrentPage = 1;
            RefreshCurrentPageRows();
        }

        public void Sort(string propertyName)
        {
            sortKeySelector = o => typeof(TRowViewModel).GetProperty(propertyName).GetValue(o, null);
            sortDirection = ListSortDirection.Ascending;
            RefreshCurrentPageRows();
        }

        public void Reverse(string propertyName)
        {
            sortKeySelector = o => typeof(TRowViewModel).GetProperty(propertyName).GetValue(o, null);
            sortDirection = ListSortDirection.Descending;
            RefreshCurrentPageRows();
        }

        public void FocusRelatedCode(TRowViewModel model)
        {
            OnCodeFocusRequested(new CodeFocusRequestedEventArgs(model.Path, model.Line));
        }

        public void GoToFirstPage()
        {
            AddSearchHistory();

            CurrentPage = 1;
            RefreshCurrentPageRows();
        }

        public void GoToPrevPage()
        {
            AddSearchHistory();

            if (!CanGoToPrevPage) throw new InvalidOperationException("Failed to go to previous page.");
            CurrentPage--;
            RefreshCurrentPageRows();
        }

        public void GoToNextPage()
        {
            AddSearchHistory();

            if (!CanGoToNextPage) throw new InvalidOperationException("Failed to go to next page.");
            CurrentPage++;
            RefreshCurrentPageRows();
        }

        public void GoToLastPage()
        {
            AddSearchHistory();

            CurrentPage = ((Count - 1) / PageSize) + 1;
            RefreshCurrentPageRows();
        }

        public void BackHistory()
        {
            if (!CanBackHistory) throw new InvalidOperationException("Failed to back history.");

            backedHistories.Push(new SearchHistory(filter, currentPage));

            var history = histories.Pop();
            filter = history.Filter;
            CurrentPage = history.Page;

            CanBackHistory = histories.Count > 0;
            CanForwardHistory = backedHistories.Count > 0;

            RefreshCurrentPageRows();
        }

        public void ForwardHistory()
        {
            if (!CanForwardHistory) throw new InvalidOperationException("Failed to forward history.");

            histories.Push(new SearchHistory(filter, currentPage));

            var history = backedHistories.Pop();
            filter = history.Filter;
            CurrentPage = history.Page;

            CanBackHistory = histories.Count > 0;
            CanForwardHistory = backedHistories.Count > 0;

            RefreshCurrentPageRows();
        }

        public void ClearHistories()
        {
            histories.Clear();
            backedHistories.Clear();
            CanBackHistory = histories.Count > 0;
            CanForwardHistory = backedHistories.Count > 0;
        }

        protected virtual void OnCodeFocusRequested(CodeFocusRequestedEventArgs e)
        {
            if (CodeFocusRequested != null)
            {
                CodeFocusRequested(this, e);
            }
        }

        protected virtual void OnExportReportRequired(EventArgs e)
        {
            if (ExportReportRequired != null)
            {
                ExportReportRequired(this, e);
            }
        }

        protected void OnGoToHowToSearchPageRequired(EventArgs e)
        {
            if (GoToHowToSearchPageRequired != null)
            {
                GoToHowToSearchPageRequired(this, e);
            }
        }

        private void RefreshCurrentPageRows()
        {
            var rows = (ObservableCollection<TRowViewModel>)CurrentPageRows;
            rows.Clear();

            var searchResults = (filter == null) ? Rows : Rows.Where(filter.Compile());
            if (sortKeySelector != null)
            {
                searchResults = (sortDirection == ListSortDirection.Ascending) ? searchResults.OrderBy(sortKeySelector, sortComparer) : searchResults.OrderByDescending(sortKeySelector, sortComparer);
            }

            foreach (var row in searchResults.Skip((CurrentPage - 1) * PageSize).Take(PageSize))
            {
                rows.Add(row);
            }

            Count = searchResults.Count();
            TotalPage = (Count - 1) / PageSize + 1;
            CanGoToPrevPage = CurrentPage > 1;
            CanGoToNextPage = CurrentPage < TotalPage;
        }

        private void AddSearchHistory()
        {
            histories.Push(new SearchHistory(filter, currentPage));
            backedHistories.Clear();
            CanBackHistory = histories.Count > 0;
            CanForwardHistory = backedHistories.Count > 0;
        }


        private class SearchHistory
        {
            internal Expression<Func<TRowViewModel, bool>> Filter { get; private set; }

            internal int Page { get; private set; }


            internal SearchHistory(Expression<Func<TRowViewModel, bool>> filter, int currentPage)
            {
                Filter = filter;
                Page = currentPage;
            }
        }
    }
}
